package com.me.test.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import com.me.test.filter.MyFilter;
import com.me.test.interceptor.Interceptor222;
import com.me.test.interceptor.Interceptor333;
import com.me.test.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Objects;

/**
 * todo
 *  1，WebMvcConfigurerAdapter 已过时，推荐实现 WebMvcConfigurer 接口。
 *  2，@EnableWebMvc 的作用是启用默认的 mvc 配置。在 spring 中需要开启，在 spring boot 中不要开启，自动配置会启用
 *
 * @author ME
 */
@Configuration
// @EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {

    @Autowired(required = false)
    private TokenInterceptor tokenInterceptor;

    @Value("${xssFilter.excludedUrl:''}")
    private String excludedUrl;

    @Value("${xssFilter.excludeParameter:\"\"}")
    private String excludeParameter;

    /**
     * 当配置 `com.me.token.enable = true` 时，声明的 @Bean 生效。
     * 默认情况下，未配置 `com.me.token.enable` 识别为 不匹配，可通过修改 `matchIfMissing` 改变策略
     *
     * @return Bean
     */
    @Bean
    @ConditionalOnProperty(prefix = "com.me.token", name = "enable", havingValue = "true", matchIfMissing = true)
    public TokenInterceptor tokenInterceptor() {
        return new TokenInterceptor();
    }

    /**
     * 当前 @Bean 与 yml配置 `spring.jackson.generator.WRITE_NUMBERS_AS_STRINGS=true` 保留一个即可，<p>
     * 	但是不推荐配置 `WRITE_NUMBERS_AS_STRINGS`，所有 number类型 都转成 String，颗粒度太大
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        Jackson2ObjectMapperBuilderCustomizer cunstomizer = new Jackson2ObjectMapperBuilderCustomizer() {
            @Override
            public void customize(Jackson2ObjectMapperBuilder builder) {
                // todo 设置自定义序列化器，优先级高于 builder.simpleDateFormat
                // SimpleModule simpleModule = new SimpleModule();
                // simpleModule.addSerializer(Date.class, DateTimeSerializer.INSTANCE);
                // simpleModule.addDeserializer(Date.class, DateTimeDeserializer.INSTANCE);
                // builder.modulesToInstall(simpleModule);

                // TODO 设置全局默认的 `Date` 格式化模板，并且优先级低于  @JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8")
                builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss").timeZone("GMT+8")
                        .serializerByType(Long.TYPE, ToStringSerializer.instance)
                        .serializerByType(Long.class, ToStringSerializer.instance);

                // TODO 启用 jdk8 的日期支持。实际上引入 JavaTimeModule、Jdk8Module 等依赖后，会自动注册，无需手动注册。
                //  !!! 但是但是，默认的时间格式不对，并且 `spring.jackson.date-format` 对 LocalDateTime 无效。
                //      JavaTimeModule：支持 LocalDate、LocalDateTime 等
                //      Jdk8Module：支持 jdk8的新类型，如 Optional、stream 等
                // builder.modulesToInstall(new JavaTimeModule(), new Jdk8Module(), new ParameterNamesModule());
                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
                builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(formatter))
                        .deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));

                // TODO 替代 spring.jackson.serialization.WRITE_BIGDECIMAL_AS_PLAIN=true
                builder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);

                // 方式二：
                // builder.serializerByType(BigDecimal.class, new JsonSerializer<BigDecimal>() {
                //     @Override
                //     public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                //         gen.writeString(value.toPlainString());
                //     }
                // });

                // 方式三
                // SimpleModule simpleModule = new SimpleModule()
                //         .addSerializer(new StdSerializer<BigDecimal>(BigDecimal.class) {
                //             @Override
                //             public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider provider) throws IOException {
                //                 gen.writeString(value.setScale(2, RoundingMode.HALF_UP).toPlainString());
                //             }
                //         });
                // builder.modulesToInstall(simpleModule);
            }
        };

        return cunstomizer;
    }

    /**
     * 自定义 Filter 只需要增加 `@Component` 注解，可指定 order，但是不能指定 url规则
     *
     * @return
     */
    @Bean
    public FilterRegistrationBean<MyFilter> myFilterRegistration() {
        FilterRegistrationBean<MyFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new MyFilter());
        registration.addUrlPatterns("/*");
        registration.addInitParameter("excludedUrl", excludedUrl);
        registration.addInitParameter("excludeParameter", excludeParameter);
        registration.setName("myFilter");
        registration.setOrder(Integer.MAX_VALUE);

        return registration;
    }

    /**
     * 自定义 MVC配置时（启用 @EnableWebMvc） 需重写
     */
    // @Override
    // public void addResourceHandlers(ResourceHandlerRegistry registry) {
    // 	registry.addResourceHandler("/**")
    // 			.addResourceLocations("classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/");
    // }

    /**
     * 自定义 MVC配置（也就是配置了 @EnableWebMvc）时 需重写
     */
    // @Override
    // public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {// 继承默认
    // // public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { // 自定义配置，放弃默认配置，
    // 	buildMvcMessageConverter(converters);
    // }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        if (Objects.nonNull(tokenInterceptor)) {
            // 匹配任意路径：/	/app.css	/app/file
            registry.addInterceptor(tokenInterceptor)
                    .addPathPatterns("/**")
                    .excludePathPatterns("/error", "/auth/**", "/swagger-resources/**", "/**/*.*");
        }

        // 匹配带后缀的路径：/a.jsp	/app/file.jsp
        registry.addInterceptor(new Interceptor222()).addPathPatterns("/**/*.jsp");

        // 匹配任意不限后缀的 【file.】：/file.     /file.jsp	/app/file.html
        registry.addInterceptor(new Interceptor333()).addPathPatterns("/**/file.*");
    }

    public void buildMvcMessageConverter(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        SimpleModule simpleModule = new SimpleModule();
        // simpleModule.addSerializer(BigDecimal.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        ObjectMapper objectMapper = new ObjectMapper()
                .registerModule(new ParameterNamesModule())
                .registerModule(new Jdk8Module())
                .registerModule(new JavaTimeModule())
                .registerModule(simpleModule);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(jackson2HttpMessageConverter);
    }

    /**
     * 默认欢迎页为 static/index.html 或 template/index.html。
     * 此处自定义应用欢迎页
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("static.html");
        // registry.addViewController("/").setViewName("forward:static.html");
    }

    // @Bean
    // public MimeTypes mimeTypes() {
    //     MimeTypes mimeTypes = new MimeTypes();
    //     Map<String, String> mappings = new HashMap<>();
    //     mappings.put("mjs", "application/javascript");
    //     mimeTypes.setMimeMappings(mappings);
    //     return mimeTypes;
    // }

    /**
     * 把 mjs 资源以 `application/javascript` 格式返回
     */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        // configurer.mediaType("mjs", org.springframework.http.MediaType.valueOf("application/javascript"));
    }

}
